package com.dingwen.treasure.jwt.uitl;

import com.dingwen.treasure.base.exception.AuthException;
import com.dingwen.treasure.base.util.StringUtil;
import com.dingwen.treasure.jwt.config.JwtProperties;
import com.dingwen.treasure.jwt.constant.JwtConstant;
import io.jsonwebtoken.*;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 　JWTUtil: jwt 工具类
 * 　@author dingwen
 * 　@since  2022/6/12
 */
@Component
@AllArgsConstructor
public class JwtUtil {

    /**
     * jwt 配置类
     */
    @Resource
    private JwtProperties jwtProperties;


    /**
     * 生成 令牌
     *
     * @param claims 元数据信息
     * @return {@link String}
     */
    public String generate(Map<String, Object> claims) {

        String userId = String.valueOf(claims.get(JwtConstant.USER_ID));
        String tokenType = String.valueOf(claims.get(JwtConstant.TOKEN_TYPE));

        // 过期时间
        long expirationTime = tokenType.equals(JwtConstant.APP_TOKEN) ?
                jwtProperties.getAppExpireTime() : jwtProperties.getWebExpireTime();

        // 当前时间
        Date now = new Date();

        // 加密key
        SecretKey secretKey = generalKey();

        JwtBuilder builder = Jwts.builder()
                // 数据声明信息
                .setClaims(claims)
                //id
                .setId(userId)
                // 签发者
                .setIssuer(jwtProperties.getIssuer())
                // 签发时间
                .setIssuedAt(now)
                // 签发主题
                .setSubject(jwtProperties.getSubject())
                // 过期时间
                .setExpiration(new Date(now.getTime() + expirationTime * JwtConstant.MILLIS_MINUTE))
                // 密钥
                .signWith(secretKey, SignatureAlgorithm.HS256);

        return jwtProperties.getTokenPrefix().concat(builder.compact());
    }

    /**
     * 令牌验证
     *
     * @param token 令牌
     * @return {@link Claims}
     */
    public Claims verify(String token) {
        try {
            return Jwts.parserBuilder()
                    .setSigningKey(jwtProperties.getSecretKey())
                    .build()
                    .parseClaimsJws(token.replace(jwtProperties.getTokenPrefix(), StringUtil.EMPTY))
                    .getBody();
        } catch (ExpiredJwtException e) {
            //TODO 待验证刷新功能
            throw new AuthException("令牌已过期");
        } catch (Exception e) {
            throw new AuthException("错误的令牌");
        }
    }


    /**
     * 由字符串生成加密key
     *
     * @return SecretKey
     */
    private SecretKey generalKey() {
        byte[] encodedKey = Base64.getDecoder().decode(jwtProperties.getSecretKey());
        return new SecretKeySpec(encodedKey, 0, encodedKey.length, JwtConstant.ALGORITHM_HMACSHA256);
    }

    /**
     * 刷新令牌
     *
     * @param claims 数据项
     * @return {@link String}
     */
    public String refreshToken(Claims claims) {
        HashMap<String, Object> params = getClaimsParams(claims);
        return generate(params);
    }

    /**
     * get claims params
     *
     * @param claims claims
     * @return {@link HashMap}<{@link String}, {@link Object}>
     */
    private HashMap<String, Object> getClaimsParams(Claims claims) {
        HashMap<String, Object> params = new HashMap<>(4);
        claims.put(JwtConstant.USER_ID, claims.get(JwtConstant.USER_ID));
        claims.put(JwtConstant.ACCOUNT, claims.get(JwtConstant.ACCOUNT));
        claims.put(JwtConstant.TOKEN_TYPE, claims.get(JwtConstant.TOKEN_TYPE));
        return params;
    }

    /**
     * 是否需要刷新判断
     *
     * @param claims 信息
     * @return 是否需要刷新
     */
    public boolean needRefresh(Claims claims) {
        String tokenType = claims.get(JwtConstant.TOKEN_TYPE, String.class);
        long refreshTime = StringUtils.equals(tokenType, JwtConstant.APP_TOKEN)
                ? jwtProperties.getAppRefreshTime()
                : jwtProperties.getWebRefreshTime();
        return claims.getExpiration().getTime() - System.currentTimeMillis() < refreshTime * JwtConstant.MILLIS_SECOND;
    }
}
